package cn.virens.web.components.shiro.simple.ajax;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.cors.CorsUtils;

import cn.hutool.core.util.StrUtil;
import cn.virens.common.RequestUtil;
import cn.virens.web.components.shiro.ShiroAuthInterface;
import cn.virens.web.components.shiro.comm.AjaxUtil;
import cn.virens.web.components.shiro.exception.CaptchaErrorException;
import cn.virens.web.components.shiro.token.ChannelTokenBasic;

public class AjaxAuthorizingFilter extends AuthenticatingFilter {
	private Logger logger = LoggerFactory.getLogger(AjaxAuthorizingFilter.class);

	private boolean useCaptcha = true;// 是否使用验证码
	private String captchaParam = "captcha";// 验证码的参数名
	private String usernameParam = "username";
	private String passwordParam = "password";

	@Autowired
	@Qualifier(ShiroAuthInterface.SHIRO_BEAN_NAME)
	private ShiroAuthInterface shiroAuthInterface;

	public AjaxAuthorizingFilter() {
	}

	public AjaxAuthorizingFilter(String loginUrl) {
		this.setLoginUrl(loginUrl);
	}

	@Override
	protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
		return CorsUtils.isPreFlightRequest((HttpServletRequest) request) || super.preHandle(request, response);
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		if (isLoginRequest(request, response) == false) {
			return AjaxUtil.write(request, response, AjaxUtil.failed("NoLogin", "请登录"));
		} else {
			return executeLogin(request, response);
		}
	}

	@Override
	protected AuthenticationToken createToken(String username, String password, boolean rememberMe, String host) {
		return new ChannelTokenBasic(username, password, rememberMe, host);
	}

	/**
	 * 执行登录操作
	 */
	@Override
	protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
		logger.info("executeLogin,RememberMe:" + isRememberMe(request));

		if (isUseCaptcha() && verifyCactcha(request, response) == false) {
			return onLoginFailure(null, new CaptchaErrorException(), request, response);
		} else {
			return super.executeLogin(request, response);
		}
	}

	/**
	 * 登录成功
	 */
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
		this.shiroAuthInterface.onLoginSuccess(getUsername(request), getHost(request));

		return true;
	}

	/**
	 * 登录失败
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
		this.shiroAuthInterface.onLoginFailure(getUsername(request), getHost(request));

		if (logger.isTraceEnabled()) {
			logger.error(e.getMessage(), e);
		}

		// 返回错误信息
		if (e instanceof CaptchaErrorException) {
			return AjaxUtil.write(request, response, AjaxUtil.failed("ERROR_TONKEY", "验证码错误"));
		} else if (e instanceof UnknownAccountException) {
			return AjaxUtil.write(request, response, AjaxUtil.failed("ERROR_ACCOUNT", "账号错误"));
		} else if (e instanceof IncorrectCredentialsException) {
			return AjaxUtil.write(request, response, AjaxUtil.failed("ERROR_PASSWROD", "密码错误"));
		} else {
			return AjaxUtil.write(request, response, AjaxUtil.failed(e));
		}
	}

	/**
	 * 是否需要验证码
	 * 
	 * @return 是否需要验证码
	 */
	public boolean isUseCaptcha() {
		return useCaptcha;
	}

	/**
	 * 设置是否需要验证码
	 * 
	 * @param useCaptcha 是否需要验证码
	 */
	public void setUseCaptcha(boolean useCaptcha) {
		this.useCaptcha = useCaptcha;
	}

	/**
	 * 获取验证码参数字段名
	 * 
	 * @return 验证码参数字段名
	 */
	public String getCaptchaParam() {
		return captchaParam;
	}

	/**
	 * 设置验证码参数字段名
	 * 
	 * @param captchaParam 验证码参数字段名
	 */
	public void setCaptchaParam(String captchaParam) {
		this.captchaParam = captchaParam;
	}

	/**
	 * 获取用户名参数字段名
	 * 
	 * @return 用户名参数字段名
	 */
	public String getUsernameParam() {
		return usernameParam;
	}

	/**
	 * 设置用户名参数字段名
	 * 
	 * @param usernameParam 用户名参数字段名
	 */
	public void setUsernameParam(String usernameParam) {
		this.usernameParam = usernameParam;
	}

	/**
	 * 获取密码参数字段名
	 * 
	 * @return 密码参数字段名
	 */
	public String getPasswordParam() {
		return passwordParam;
	}

	/**
	 * 设置密码参数字段名
	 * 
	 * @param passwordParam 密码参数字段名
	 */
	public void setPasswordParam(String passwordParam) {
		this.passwordParam = passwordParam;
	}

	/**
	 * 获取当前客户端地址
	 */
	@Override
	protected String getHost(ServletRequest request) {
		return RequestUtil.getRemoteAddr((HttpServletRequest) request);
	}

	/**
	 * 获取当前提交的验证码
	 * 
	 * @param  request ServletRequest
	 * @return         当前提交的验证码
	 */
	protected String getCaptcha(ServletRequest request) {
		return WebUtils.getCleanParam(request, getCaptchaParam());
	}

	/**
	 * 获取当前提交的用户名
	 * 
	 * @param  request ServletRequest
	 * @return         当前提交的用户名
	 */
	protected String getUsername(ServletRequest request) {
		return WebUtils.getCleanParam(request, getUsernameParam());
	}

	/**
	 * 获取当前提交的密码
	 * 
	 * @param  request ServletRequest
	 * @return         当前提交的密码
	 */
	protected String getPassword(ServletRequest request) {
		return WebUtils.getCleanParam(request, getPasswordParam());
	}

	/**
	 * 获取session缓存的验证码
	 * 
	 * @param  request  ServletRequest
	 * @param  response ServletResponse
	 * @return          缓存的验证码
	 */
	protected String getCaptcha(ServletRequest request, ServletResponse response) {
		Subject subject = getSubject(request, response);
		if (subject == null) { return null; }

		Session session = subject.getSession(false);
		if (session == null) { return null; }

		return String.valueOf(session.getAttribute(getCaptchaParam()));
	}

	/**
	 * 检验 验证码
	 * 
	 * @param  request  ServletRequest
	 * @param  response ServletResponse
	 * @return          验证码校验结果
	 */
	protected boolean verifyCactcha(ServletRequest request, ServletResponse response) {
		String tcode2 = getCaptcha(request, response);
		String tcode1 = getCaptcha(request);

		logger.debug("验证码：{}/{}", tcode1, tcode2);

		return StrUtil.equalsIgnoreCase(tcode1, tcode2);
	}

	@Override
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
		return createToken(getUsername(request), getPassword(request), false, getHost(request));
	}

}
